#pragma once
#include "ZhicheEntity.h"
#include "MarkerMgr.h"

class ModelMgr : protected IEntityMgr {
public:
	static ModelMgr* get() {
		static auto instance = new ModelMgr;
		return instance;
	}

	/**@brief called per frame, at rendering thread */
	void updateEntities() override {
		bool hasNewData = Cache::useNewlyRcvdModels();

		handleSQStatus();

		const auto& allArgMap = Cache::modelArgMap();
		for (auto& kv : allArgMap) {
			auto& arg = kv.value;
			Cache::increaseUpdateFrameCnt(arg.ID);
			if (!arg.identified) {/**@deprecated currently not used*/
				removeModelAndGlobalCache(arg.ID);
				continue;
			}
			if (hasNewData)
				updateModel(arg);
		}
		updateZhiche();

		clearExpiredModels();
	}

	void clear() override {
		auto modelArgMap = Cache::modelArgMap();
		for (auto& kv: modelArgMap) {
			auto& arg = kv.value;
			removeModel(arg.ID);
			Cache::removeModel(arg.ID);
		}
		Scene::remove(m_zhiche);
		m_zhiche = nullptr;

		removeSQZhicheCircle();
	}

	/** do not turn to single-instance mode, may lead to diff-thread-create and unnecessary init*/
	ZhicheEntity* zhiche() { return m_zhiche; }

	void handleZhicheDoor(HDir side, Switch status) {
		if (!m_zhiche) return;
		m_zhiche->handleDoor(side, status);
	}
	void resetZhicheDoor() {
		handleZhicheDoor(HDir::Left, Switch::Off);
		handleZhicheDoor(HDir::Right, Switch::Off);
	}

private:
	ModelMgr() = default;

	/** @warning condition exists: ID keep the same, but ModelType change! */
	void updateModel(const ModelArg& arg) {
		auto ID = arg.ID;
		ModelEntity* model;
		if (!m_modelMap.contains(ID))
			model = addModel(arg);
		else {
			model = m_modelMap[ID];
			/**@note same target object, model type recognition changed*/
			if (model->type() != arg.type) {
				removeModel(ID);
				model = addModel(arg);
			}
		}
		model->update();

		MarkerMgr::get()->updateModelAccFD(ID);
	}

	void updateZhiche() {
		updateSQZhicheCircle();

		if (!Cache::zhicheArg().isValid())
			return;
		if (!m_zhiche) {
			m_zhiche = new ZhicheEntity;
			Scene::add(m_zhiche);
		}
		m_zhiche->update();
	}

	ModelEntity* addModel(const ModelArg& arg) {
		auto model = new ModelEntity(arg.type, arg.ID);
		m_modelMap.insert(arg.ID, model);
		Scene::add(model);
		return model;
	}

	void removeModel(int ID) {
		Scene::remove(m_modelMap.take(ID));
		MarkerMgr::get()->removeModelAccFD(ID);
	}

	/**
	 *@important be careful: do not remove the global-cached model-datas too early!
	 * because [in a same frame], many other place have to rely on the global-cached model-datas!
	 */
	void removeModelAndGlobalCache(int ID) {
		removeModel(ID);
		Cache::removeModel(ID);
	}

	void clearExpiredModels() {
		const auto ExpireUpdateCnt = Config::Mechanism_ExpireUpdateCnt;
		const auto ExpireFrameCnt = Config::Mechanism_ExpireFrameCnt;
		const auto modelArgMap = Cache::modelArgMap();
		for (const auto& kv: modelArgMap) {
			auto& arg = kv.value;
			if (arg.updateCnt >= ExpireUpdateCnt || arg.updateFrameCnt >= ExpireFrameCnt)
				removeModelAndGlobalCache(arg.ID);
		}
	}

	CMap<int, ModelEntity*> m_modelMap;
	ZhicheEntity* m_zhiche = nullptr;

	/******************************* SQ-Android *******************************/
	/******************************* SQ-Android *******************************/
	void removeSQZhicheCircle() {
		if (!(IsProject_SQ || AsPretendedProject_SQ))
			return;
		MarkerMgr::get()->remove(MarkerType::ZhicheCircle);
	}

	void updateSQZhicheCircle() {
		if (!(IsProject_SQ || AsPretendedProject_SQ))
			return;
		if (!Cache::isReceivingData()) {
			removeSQZhicheCircle();
			return;
		}
		MarkerMgr::get()->update(MarkerType::ZhicheCircle);
	}

	void handleSQStatus() {
		if (!(IsProject_SQ || AsPretendedProject_SQ))
			return;
		const auto& sqStatus = Cache::sqStatus();
		/**@note FCW has highest priority: 1 */
		bool isFCWValid = !Enum_isInvalidOrOff(sqStatus.fcw);
		if (isFCWValid) {
			int closestFrontID = Cache::getModelIdClosestToOrigin();
			if (closestFrontID != -1) {
				auto& model = Cache::modelArgRef(closestFrontID);
				if (sqStatus.fcw == sq::FCW::Yellow) model.color = "yellow";
				else if (sqStatus.fcw == sq::FCW::Red) model.color = "red";
			}
		} else {
			/**@note ACAS has priority: 2 */
			bool isACASValid = !Enum_isInvalidOrOff(sqStatus.acas);
			if (isACASValid) {
				HDir towards = (sqStatus.acas == sq::ACAS::Left ? HDir::Right : HDir::Left);
				auto IDs = Cache::getRangeModelIDs(sq::TARGET_RANGE::ACAS_H_RADIUS, sq::TARGET_RANGE::ACAS_V_RADIUS, towards);
				for (auto id : IDs) {
					auto& model = Cache::modelArgRef(id);
					model.color = "yellow";
				}
			} else {
				/**@note ALC has priority: 3 */
				bool isALCValid = Enum_isInvalidOrOff(sqStatus.noa) && !Enum_isInvalidOrOff(sqStatus.alc);
				if (isALCValid) {
					HDir towards = (sqStatus.alc == sq::ALC::Left ? HDir::Left : HDir::Right);
					auto IDs = Cache::getRangeModelIDs(sq::TARGET_RANGE::ALC_H_RADIUS, sq::TARGET_RANGE::ALC_V_RADIUS, towards);
					for (auto id : IDs) {
						auto& model = Cache::modelArgRef(id);
						model.color = "yellow";
					}
				} else {
					/**@note ACC has priority: 4 */
					bool isACCValid = !Enum_isInvalidOrOff(sqStatus.acc);
					if (isACCValid) {
						int closestFrontID = Cache::getModelIdClosestToOrigin();
						if (closestFrontID != -1) {
							auto& model = Cache::modelArgRef(closestFrontID);
							if (isACCValid && sqStatus.acc == sq::ACC::Follow)
								model.color = "blue";
						}
					}
				}
			}
		}
	}
};